/*
 * Created by JFormDesigner on Tue Feb 20 14:38:39 CST 2024
 */

package com.franklin.ideaplugin.mybatisrunner.ui;

import javax.swing.*;
import com.formdev.flatlaf.extras.*;

import com.franklin.ideaplugin.api.ui.editor.CustomEditor;
import com.franklin.ideaplugin.api.utils.EditorUtils;
import com.franklin.ideaplugin.api.utils.SwingUtils;
import com.franklin.ideaplugin.mybatisrunner.cache.Cache;
import com.franklin.ideaplugin.mybatisrunner.constant.Constants;
import com.franklin.ideaplugin.mybatisrunner.event.SqlEventFactory;
import com.franklin.ideaplugin.mybatisrunner.event.events.*;
import com.franklin.ideaplugin.mybatisrunner.icon.PluginIcons;
import com.franklin.ideaplugin.mybatisrunner.psi.MybatisPsiElement;
import com.franklin.ideaplugin.mybatisrunner.runner.DatabaseToolRunner;
import com.franklin.ideaplugin.mybatisrunner.utils.SqlUtils;
import com.franklin.ideaplugin.mybatisrunner.xml.MybatisSqlContext;
import com.franklin.ideaplugin.mybatisrunner.xml.node.MybatisNodeHandlers;
import com.franklin.ideaplugin.mybatisrunner.xml.params.ChooseParam;
import com.franklin.ideaplugin.mybatisrunner.xml.params.MybatisParam;
import com.franklin.ideaplugin.mybatisrunner.xml.params.NormalParam;
import com.franklin.ideaplugin.mybatisrunner.xml.params.PredicateParam;
import com.franklin.tool.event.annotation.AddEventListener;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.editor.CaretModel;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.LogicalPosition;
import com.intellij.openapi.editor.ScrollingModel;
import com.intellij.openapi.editor.ex.EditorEx;
import com.intellij.openapi.editor.ex.util.EditorUtil;
import com.intellij.openapi.wm.WindowManager;
import com.intellij.openapi.wm.impl.IdeGlassPaneImpl;
import com.intellij.pom.Navigatable;
import com.intellij.psi.PsiElement;
import com.intellij.psi.xml.XmlTag;
import com.intellij.sql.SqlFileType;
import com.intellij.ui.components.*;
import com.intellij.uiDesigner.core.*;

import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.util.*;
import java.util.List;

import com.intellij.util.ui.FormBuilder;
import org.jdesktop.swingx.*;

/**
 * @author lenovo
 */
public class SqlRunnerForm extends JFrame {

    private MybatisPsiElement mybatisPsiElement;

    private MybatisSqlContext mybatisSqlContext;

    private CustomEditor customEditor;

    private XmlTag xmlTag;

    private List<MybatisParam> mybatisParamList;

    private DatabaseToolRunner databaseToolRunner;

    public SqlRunnerForm(MybatisPsiElement mybatisPsiElement) {
        initComponents();
        this.mybatisPsiElement = mybatisPsiElement;
        SqlEventFactory.getSqlMulticaster(mybatisPsiElement).addMemberListeners(this);
        this.mybatisSqlContext = Cache.getInstance(mybatisPsiElement.getProject())
                .getMybatisParams(mybatisPsiElement);
        this.customEditor = new CustomEditor(mybatisPsiElement.getProject(), SqlFileType.INSTANCE);
        this.xmlTag = mybatisPsiElement.getSqlXmlTag();
        this.mybatisParamList = MybatisNodeHandlers.resolveParams(xmlTag,mybatisSqlContext);
        init();
    }

    private void initComponents() {
        // JFormDesigner - Component initialization - DO NOT MODIFY  //GEN-BEGIN:initComponents  @formatter:off
        splitPane1 = new JSplitPane();
        toolBarPanel = new JPanel();
        runBtn = new JButton();
        gotoBtn = new JButton();
        mainPanel = new JPanel();
        bottomSpliPane = new JSplitPane();
        paramParentPanel = new JPanel();
        scrollPane = new JBScrollPane();
        paramPanel = new JPanel();
        sqlPanel = new JPanel();

        //======== this ========
        setMinimumSize(new Dimension(1280, 720));
        setPreferredSize(new Dimension(1280, 720));
        setIconImage(new ImageIcon(getClass().getResource("/icons/mybatis.png")).getImage());
        var contentPane = getContentPane();
        contentPane.setLayout(new GridLayoutManager(1, 1, new Insets(0, 0, 0, 0), -1, -1));

        //======== splitPane1 ========
        {
            splitPane1.setOrientation(JSplitPane.VERTICAL_SPLIT);
            splitPane1.setDividerSize(0);
            splitPane1.setDividerLocation(49);

            //======== toolBarPanel ========
            {
                toolBarPanel.setLayout(new FlowLayout(FlowLayout.LEFT));

                //---- runBtn ----
                runBtn.setIcon(null);
                runBtn.setRolloverIcon(null);
                toolBarPanel.add(runBtn);

                //---- gotoBtn ----
                gotoBtn.setIcon(new FlatSVGIcon("icons/arrowRight_dark.svg"));
                gotoBtn.setRolloverIcon(new FlatSVGIcon("icons/arrowRight.svg"));
                gotoBtn.setToolTipText("\u8df3\u8f6cSQL");
                toolBarPanel.add(gotoBtn);
            }
            splitPane1.setTopComponent(toolBarPanel);

            //======== mainPanel ========
            {
                mainPanel.setLayout(new GridLayoutManager(1, 1, new Insets(0, 0, 0, 0), -1, -1));

                //======== bottomSpliPane ========
                {
                    bottomSpliPane.setDividerSize(2);
                    bottomSpliPane.setDividerLocation(200);

                    //======== paramParentPanel ========
                    {
                        paramParentPanel.setLayout(new GridLayoutManager(1, 1, new Insets(0, 0, 0, 0), -1, -1));

                        //======== scrollPane ========
                        {

                            //======== paramPanel ========
                            {
                                paramPanel.setLayout(new VerticalLayout(5));
                            }
                            scrollPane.setViewportView(paramPanel);
                        }
                        paramParentPanel.add(scrollPane, new GridConstraints(0, 0, 1, 1,
                            GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH,
                            GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW,
                            GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW,
                            null, null, null));
                    }
                    bottomSpliPane.setLeftComponent(paramParentPanel);

                    //======== sqlPanel ========
                    {
                        sqlPanel.setLayout(new GridLayoutManager(1, 1, new Insets(0, 0, 0, 0), -1, -1));
                    }
                    bottomSpliPane.setRightComponent(sqlPanel);
                }
                mainPanel.add(bottomSpliPane, new GridConstraints(0, 0, 1, 1,
                    GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH,
                    GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW,
                    GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW,
                    null, null, null));
            }
            splitPane1.setBottomComponent(mainPanel);
        }
        contentPane.add(splitPane1, new GridConstraints(0, 0, 1, 1,
            GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH,
            GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW,
            GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW,
            null, null, null));
        pack();
        setLocationRelativeTo(getOwner());
        // JFormDesigner - End of component initialization  //GEN-END:initComponents  @formatter:on
    }

    // JFormDesigner - Variables declaration - DO NOT MODIFY  //GEN-BEGIN:variables  @formatter:off
    private JSplitPane splitPane1;
    private JPanel toolBarPanel;
    private JButton runBtn;
    private JButton gotoBtn;
    private JPanel mainPanel;
    private JSplitPane bottomSpliPane;
    private JPanel paramParentPanel;
    private JBScrollPane scrollPane;
    private JPanel paramPanel;
    private JPanel sqlPanel;
    // JFormDesigner - End of variables declaration  //GEN-END:variables  @formatter:on

    /**
     * 初始化
     */
    private void init() {
        loadParamHistory();
        databaseToolRunner = new DatabaseToolRunner(mybatisPsiElement);
        sqlPanel.setLayout(SwingUtils.createLayoutManager(1, 1));
        SwingUtils.addCell(sqlPanel, customEditor, 0, 0, new Dimension(-1, -1));
        bottomSpliPane.setDividerLocation(this.getWidth() / 3);
        initRunBtn();
        initGotoBtn();
        initFrame();
        initEditor();
        refreshSql();
    }

    /**
     * 初始化编辑器
     */
    private void initEditor(){

    }

    /**
     * 加载历史参数
     */
    private void loadParamHistory() {
        Map<String, String> paramElMap = mybatisSqlContext.getParamElMap();
        Map<String, String> paramMap = mybatisSqlContext.getParamMap();
        Set<String> testCases = mybatisSqlContext.getTestCases();
        mybatisParamList.stream()
                .filter(mybatisParam -> mybatisParam instanceof NormalParam)
                .forEach(mybatisParam -> {
                    Optional.ofNullable(paramMap.get(mybatisParam.getParamName()))
                            .ifPresent(mybatisParam::load);
                    paramElMap.put(mybatisParam.getParamName(),mybatisParam.getElExpression());
                });
        mybatisParamList.stream()
                .filter(mybatisParam -> mybatisParam instanceof PredicateParam || mybatisParam instanceof ChooseParam)
                .forEach(mybatisParam -> {
                    if (testCases.contains(mybatisParam.getElExpression())){
                        mybatisParam.load(mybatisParam.getElExpression());
                    }
                });
    }

    /**
     * 初始化弹窗
     */
    private void initFrame() {
        this.setTitle(mybatisPsiElement.getKey());
        //包装为idea的glassPane，实现ctrl+ F/R 搜索/替换
        JRootPane rootPane = this.getRootPane();
        IdeGlassPaneImpl ideGlassPane = new IdeGlassPaneImpl(rootPane);
        rootPane.setGlassPane(ideGlassPane);
        this.addWindowListener(new WindowAdapter() {
            @Override
            public void windowClosed(WindowEvent e) {

            }

            @Override
            public void windowClosing(WindowEvent e) {
                //主题下线
                SqlEventFactory sqlEventFactory = SqlEventFactory.getInstance(mybatisPsiElement.getProject());
                sqlEventFactory.removeMulticaster(mybatisPsiElement);

                //窗口下线
                SqlRunnerFormFactory.removeForm(mybatisPsiElement);

                dispose();
            }
        });
    }

    /**
     * 初始化执行按钮
     */
    private void initRunBtn() {
        runBtn.setIcon(PluginIcons.RUN);
        runBtn.setRolloverIcon(PluginIcons.RUN_LOCAL);
        runBtn.addActionListener(e -> {
            Cache.getInstance(mybatisPsiElement.getProject())
                    .cacheMybatisParams(mybatisPsiElement, mybatisSqlContext);
            databaseToolRunner.executeSql(customEditor.getText());
        });
    }

    /**
     * 初始化goto按钮
     */
    private void initGotoBtn(){
        gotoBtn.addActionListener(e -> {
            Navigatable navigatable = (Navigatable) mybatisPsiElement.getPsiElement();
            navigatable.navigate(true);
        });
    }

    /**
     * 展示表单
     */
    public void showForm() {
        JFrame frame = WindowManager.getInstance().getFrame(mybatisPsiElement.getProject());
        this.setLocationRelativeTo(frame);
        ApplicationManager.getApplication().invokeLater(() -> {
            this.setVisible(true);
        });
    }

    /**
     * 初始化
     */
    public void initParamPanel() {
        paramPanel.removeAll();
        mybatisParamList.stream()
                .filter(MybatisParam::isVisible)
                .forEach(mybatisParam -> {
                    JComponent component = mybatisParam.buildComponent(() -> {
                        SqlEventFactory.getSqlMulticaster(mybatisPsiElement)
                                .publishEvent(mybatisParam.buildEvent());
                    });

                    //添加跳转按钮
                    JBPanel<JBPanel> jbPanel = new JBPanel<>();
                    jbPanel.setLayout(new FlowLayout(FlowLayout.LEFT));

                    JButton gotoBtn = new JButton(PluginIcons.ARROW_RIGHT_DARK);
                    gotoBtn.setRolloverIcon(PluginIcons.ARROW_RIGHT);
                    gotoBtn.setToolTipText("跳转代码");
                    gotoBtn.addActionListener(e -> {
                        PsiElement psiElement = mybatisParam.getPsiElement();
                        if (Objects.nonNull(psiElement)) {
                            Navigatable navigationElement = (Navigatable) psiElement.getNavigationElement();
                            navigationElement.navigate(true);
                        }
                    });
                    gotoBtn.setSize(new Dimension(30, -1));

                    SwingUtils.addCell(
                            jbPanel,
                            gotoBtn,
                            0,
                            0,
                            new Dimension(30, -1)
                    );
                    SwingUtils.addCell(
                            jbPanel,
                            component,
                            0,
                            1,
                            new Dimension(Integer.MAX_VALUE, -1)
                    );

                    paramPanel.add(jbPanel);
                });

//        paramPanel.addMouseListener(new MouseAdapter() {
//            @Override
//            public void mouseClicked(MouseEvent e) {
//                refreshSql();
//            }
//        });
    }

    /**
     * 更新元素
     * @param mybatisPsiElementRefreshEvent
     */
    @AddEventListener
    private void mybatisPsiElementRefreshEvent(MybatisPsiElementRefreshEvent mybatisPsiElementRefreshEvent){
        MybatisPsiElement mybatisPsiElement = mybatisPsiElementRefreshEvent.getMybatisPsiElement();
        this.mybatisPsiElement = mybatisPsiElement;
        this.xmlTag = mybatisPsiElement.getSqlXmlTag();
    }

    /**
     * 添加断言
     *
     * @param addTestEvent
     */
    @AddEventListener
    private void addTestEvent(AddTestEvent addTestEvent) {
        mybatisSqlContext.getTestCases().add(addTestEvent.getElExpression());
        refreshSql();
    }

    /**
     * 删除断言
     *
     * @param removeTestEvent
     */
    @AddEventListener
    private void removeTestEvent(RemoveTestEvent removeTestEvent) {
        mybatisSqlContext.getTestCases().remove(removeTestEvent.getElExpression());
        refreshSql();
    }

    /**
     * 更新断言
     *
     * @param updateTestEvent
     */
    @AddEventListener
    private void updateTestEvent(UpdateTestEvent updateTestEvent) {
        mybatisSqlContext.getTestCases().remove(updateTestEvent.getLastExpression());
        mybatisSqlContext.getTestCases().add(updateTestEvent.getElExpression());
        refreshSql();
    }

    /**
     * 参数变更
     *
     * @param paramUpdateEvent
     */
    @AddEventListener
    private void paramUpdateEvent(ParamUpdateEvent paramUpdateEvent) {
        mybatisSqlContext.getParamMap().put(paramUpdateEvent.getParamName(), paramUpdateEvent.getValue());
        refreshSql();
    }

    /**
     * 构建SQL
     *
     * @return
     */
    private String buildSql() {
        return MybatisNodeHandlers.resolveSql(xmlTag, mybatisSqlContext);
    }

    /**
     * 刷新SQL
     */
    private void refreshSql() {
        String resultSql = getResultSql();
        resultSql = SqlUtils.format(resultSql);
        Editor editor = customEditor.getEditor(true);

        // 获取 CaretModel 实例
        CaretModel caretModel = editor.getCaretModel();

        // 获取当前光标所在的逻辑位置
        LogicalPosition logicalPosition = caretModel.getLogicalPosition();

        customEditor.setText(resultSql, SqlFileType.INSTANCE);

        // 滚动到光标位置
        editor = customEditor.getEditor();

        Editor finalEditor = editor;
        SwingUtilities.invokeLater(() -> {
            EditorUtils.smoothScrollToLine(finalEditor,logicalPosition.line,0);
        });

    }

    /**
     * 获取最终SQL
     *
     * @return
     */
    private String getResultSql() {
        String sql = buildSql();
        mybatisParamList.stream()
                .filter(mybatisParam -> mybatisParam instanceof NormalParam)
                .forEach(mybatisParam -> mybatisSqlContext.getParamMap().putIfAbsent(mybatisParam.getParamName(), ""));
        for (Map.Entry<String, String> entry : mybatisSqlContext.getParamMap().entrySet()) {
            //参数名
            String paramName = entry.getKey();
            //参数值
            String value = entry.getValue();
            //el表达式
            String elExpression = mybatisSqlContext.getParamElMap().getOrDefault(paramName, "");
            sql = sql.replace(elExpression, value);
        }
        return sql;
    }
}
